/*-
 * SMSCharacterSet.java
 *
 * $LastChangedBy: stein $
 * $LastChangedRevision: 37 $
 * $LastChangedDate: 2006-11-30 23:06:29 +0000 (Thu, 30 Nov 2006) $
 * $HeadURL: svn://localhost/SMS/src/com/googlecode/attention/sms/pdu/UDHI.java $
 *
 * Copyright (c) 2007, Stein Gunnar Bakkeby
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Stein Gunnar Bakkeby nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.googlecode.attention.util;

/**
 * Utility that converts between SMS character sets, e.g. CP437,
 * CP850, GSM Default Alphabet, and UCS2.
 *
 * @author Stein Gunnar Bakkeby
 */
public class SMSCharacterSet {

  /* Static field values */
  /**
   * This indicates whether the conversion of text has failed or not.
   */
  public static boolean convertFailure = false;
  /**
   * This represents the character to use as replacement if the
   * conversion fails, the default value is that of the space character.
   * This is only made public to allow for the replacement character to
   * be modified, but take care when changing this value, especially if
   * multiple threads are using this static object.
   */
  public static int convertFailureReplacement = 32;
  
  /**
   * Mapping from CP850 to CP437.
   */
  private static final int[] CP850_TO_CP437 = {
      0,   1,   2,   3,   4,   5,   6,   7,   8,   9, //   0 -   9
     10,  11,  12,  13,  14,  15,  16,  17,  18,  19, //  10 -  19
     20,  21,  22,  23,  24,  25,  26,  27,  28,  29, //  20 -  29
     30,  31,  32,  33,  34,  35,  36,  37,  38,  39, //  30 -  39
     40,  41,  42,  43,  44,  45,  46,  47,  48,  49, //  40 -  49
     50,  51,  52,  53,  54,  55,  56,  57,  58,  59, //  50 -  59
     60,  61,  62,  63,  64,  65,  66,  67,  68,  69, //  60 -  69
     70,  71,  72,  73,  74,  75,  76,  77,  78,  79, //  70 -  79
     80,  71,  82,  83,  84,  85,  86,  87,  88,  89, //  80 -  89
     90,  91,  82,  93,  94,  95,  96,  97,  98,  99, //  90 -  99
    100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100 - 109
    110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110 - 119
    120, 121, 122, 123, 124, 125, 126, 127, 128, 129, // 120 - 129
    130, 131, 132, 133, 134, 135, 136, 137, 138, 139, // 130 - 139
    140, 141, 142, 143, 144, 145, 146, 147, 148, 149, // 140 - 149
    150, 151, 152, 153, 154,  -1, 156,  -1,  -1, 159, // 150 - 159
    160, 161, 162, 163, 164, 165, 166, 167, 168,  -1, // 160 - 169
    170, 171, 172, 173, 174, 175, 176, 177, 178, 179, // 170 - 179
    180,  -1,  -1,  -1,  -1, 185, 186, 187, 188, 155, // 180 - 189
    157, 191, 192, 193, 194, 195, 196, 197,  -1,  -1, // 190 - 199
    200, 201, 202, 203, 204, 205, 206,  -1,  -1,  -1, // 200 - 209
     -1,  -1,  -1,  -1,  -1,  -1,  -1, 217, 218, 219, // 210 - 219
    220,  -1,  -1, 223,  -1, 225,  -1,  -1,  -1,  -1, // 220 - 229
    230,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 230 - 239
     -1, 241,  -1,  -1,  -1,  -1, 246,  -1, 248, 249, // 240 - 249
    250,  -1,  -1, 253, 254, 255                      // 250 - 255
  };
  
  /**
   * Mapping from CP437 to CP850.
   */
  private static final int[] CP437_TO_CP850 = {
      0,   1,   2,   3,   4,   5,   6,   7,   8,   9, //   0 -   9
     10,  11,  12,  13,  14,  15,  16,  17,  18,  19, //  10 -  19
     20,  21,  22,  23,  24,  25,  26,  27,  28,  29, //  20 -  29
     30,  31,  32,  33,  34,  35,  36,  37,  38,  39, //  30 -  39
     40,  41,  42,  43,  44,  45,  46,  47,  48,  49, //  40 -  49
     50,  51,  52,  53,  54,  55,  56,  57,  58,  59, //  50 -  59
     60,  61,  62,  63,  64,  65,  66,  67,  68,  69, //  60 -  69
     70,  71,  72,  73,  74,  75,  76,  77,  78,  79, //  70 -  79
     80,  71,  82,  83,  84,  85,  86,  87,  88,  89, //  80 -  89
     90,  91,  82,  93,  94,  95,  96,  97,  98,  99, //  90 -  99
    100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100 - 109
    110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110 - 119
    120, 121, 122, 123, 124, 125, 126, 127, 128, 129, // 120 - 129
    130, 131, 132, 133, 134, 135, 136, 137, 138, 139, // 130 - 139
    140, 141, 142, 143, 144, 145, 146, 147, 148, 149, // 140 - 149
    150, 151, 152, 153, 154, 189, 156, 190,  -1, 159, // 150 - 159
    160, 161, 162, 163, 164, 165, 166, 167, 168,  -1, // 160 - 169
    170, 171, 172, 173, 174, 175, 176, 177, 178, 179, // 170 - 179
    180,  -1,  -1,  -1,  -1, 185, 186, 187, 188,  -1, // 180 - 189
     -1, 191, 192, 193, 194, 195, 196, 197,  -1,  -1, // 190 - 199
    200, 201, 202, 203, 204, 205, 206,  -1,  -1,  -1, // 200 - 209
     -1,  -1,  -1,  -1,  -1,  -1,  -1, 217, 218, 219, // 210 - 219
    220,  -1,  -1, 223,  -1, 225,  -1,  -1,  -1,  -1, // 220 - 229
    230,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 230 - 239
     -1, 241,  -1,  -1,  -1,  -1, 246,  -1, 248, 249, // 240 - 249
    250,  -1,  -1, 253, 254, 255                      // 250 - 255
  };
  
  /**
   * Mapping from GSM 7bit Default Alphabet to CP437.
   */
  private static final int[] GSM7_TO_CP437 = {
     64, 156,  36, 157, 138, 130, 151, 141, 149, 128, //   0 -   9
     10,  -1,  -1,  13, 143, 134,  -1,  95, 232, 226, //  10 -  19
     -1, 234,  -1,  -1, 228, 233,  -1,  27, 146, 145, //  20 -  29
    225, 144,  32,  33,  34,  35,  -1,  37,  38,  39, //  30 -  39
     40,  41,  42,  43,  44,  45,  46,  47,  48,  49, //  40 -  49
     50,  51,  52,  53,  54,  55,  56,  57,  58,  59, //  50 -  59
     60,  61,  62,  63, 173,  65,  66,  67,  68,  69, //  60 -  69
     70,  71,  72,  73,  74,  75,  76,  77,  78,  79, //  70 -  79
     80,  81,  82,  83,  84,  85,  86,  87,  88,  89, //  80 -  89
     90, 142, 153, 165, 154,  -1, 168,  97,  98,  99, //  90 -  99
    100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100 - 109
    110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110 - 119
    120, 121, 122, 132, 148, 164, 129, 133            // 120 - 127
  };
  
  /**
   * Mapping from CP437 to GSM 7bit Default Alphabet.
   */
  private static final int[] CP437_TO_GSM7 = {
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //   0 -   9
     10,  -1,  -1,  13,  -1,  -1,  -1,  -1,  -1,  -1, //  10 -  19
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //  20 -  29
     -1,  -1,  32,  33,  34,  35,   2,  37,  38,  39, //  30 -  39
     40,  41,  42,  43,  44,  45,  46,  47,  48,  49, //  40 -  49
     50,  51,  52,  53,  54,  55,  56,  57,  58,  59, //  50 -  59
     60,  61,  62,  63,   0,  65,  66,  67,  68,  69, //  60 -  69
     70,  71,  72,  73,  74,  75,  76,  77,  78,  79, //  70 -  79
     80,  81,  82,  83,  84,  85,  86,  87,  88,  89, //  80 -  89
     90,3516,3503,3518,3476,  17,  -1,  97,  98,  99, //  90 -  99
    100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100 - 109
    110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110 - 119
    120, 121, 122,3496,3520,3497,3517,  -1,  -1, 126, // 120 - 129
      5,  -1, 123, 127,  15,  -1,  -1,  -1,   4,  -1, // 130 - 139
     -1,   7,  91,  14,  31,  29,  28,  -1, 124,   8, // 140 - 149
     -1,   6,  -1,  92,  94,  -1,   1,   3,  -1,  -1, // 150 - 159
     -1,  -1,  -1,  -1, 125,  93,  -1,  -1,  96,  -1, // 160 - 169
     -1,  -1,  -1,  64,  -1,  -1,  -1,  -1,  -1,  -1, // 170 - 179
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 180 - 189
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 190 - 199
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 200 - 209
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 210 - 219
     -1,  -1,  -1,  -1,  -1,  30,  19,  -1,  24,  -1, // 220 - 229
     -1,  -1,  18,  25,  21,  -1,  -1,  -1,  -1,  -1, // 230 - 239
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 240 - 249
     -1,  -1,  -1,  -1,  -1,  -1                      // 250 - 255
  };
  
  /**
   * Mapping from GSM 7bit Default Alphabet to CP850.
   */
  private static final int[] GSM7_TO_CP850 = {
     64, 156,  36, 190, 138, 130, 151, 141, 149, 128, //   0 -   9
     10, 157, 155,  13, 143, 134,  -1,  95,  -1, 226, //  10 -  19
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  27, 146, 145, //  20 -  29
    225, 144,  32,  33,  34,  35, 207,  37,  38,  39, //  30 -  39
     40,  41,  42,  43,  44,  45,  46,  47,  48,  49, //  40 -  49
     50,  51,  52,  53,  54,  55,  56,  57,  58,  59, //  50 -  59
     60,  61,  62,  63, 173,  65,  66,  67,  68,  69, //  60 -  69
     70,  71,  72,  73,  74,  75,  76,  77,  78,  79, //  70 -  79
     80,  81,  82,  83,  84,  85,  86,  87,  88,  89, //  80 -  89
     90, 142, 153, 165, 154, 245, 168,  97,  98,  99, //  90 -  99
    100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100 - 109
    110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110 - 119
    120, 121, 122, 132, 148, 164, 129, 133            // 120 - 127
  };
  
  /**
   * Mapping from CP850 to GSM 7bit Default Alphabet.
   */
  private static final int[] CP850_TO_GSM7 = {
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //   0 -   9
     10,  -1,  -1,  13,  -1,  -1,  -1,  -1,  -1,  -1, //  10 -  19
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //  20 -  29
     -1,  -1,  32,  33,  34,  35,   2,  37,  38,  39, //  30 -  39
     40,  41,  42,  43,  44,  45,  46,  47,  48,  49, //  40 -  49
     50,  51,  52,  53,  54,  55,  56,  57,  58,  59, //  50 -  59
     60,  61,  62,  63,   0,  65,  66,  67,  68,  69, //  60 -  69
     70,  71,  72,  73,  74,  75,  76,  77,  78,  79, //  70 -  79
     80,  81,  82,  83,  84,  85,  86,  87,  88,  89, //  80 -  89
     90,3516,3503,3518,3476,  17,  -1,  97,  98,  99, //  90 -  99
    100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100 - 109
    110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110 - 119
    120, 121, 122,3496,3520,3497,3517,  -1,  -1, 126, // 120 - 129
      5,  -1, 123, 127,  15,  -1,  -1,  -1,   4,  -1, // 130 - 139
     -1,   7,  91,  14,  31,  29,  28,  -1, 124,   8, // 140 - 149
     -1,   6,  -1,  92,  94,  12,   1,  11,  -1,  -1, // 150 - 159
     -1,  -1,  -1,  -1, 125,  93,  -1,  -1,  96,  -1, // 160 - 169
     -1,  -1,  -1,  64,  -1,  -1,  -1,  -1,  -1,  -1, // 170 - 179
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 180 - 189
      3,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 190 - 199
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  36,  -1,  -1, // 200 - 209
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 210 - 219
     -1,  -1,  -1,  -1,  -1,  30,  19,  -1,  -1,  -1, // 220 - 229
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 230 - 239
     -1,  -1,  -1,  -1,  -1,  95,  -1,  -1,  -1,  -1, // 240 - 249
     -1,  -1,  -1,  -1,  -1,  -1                      // 250 - 255
  };
  
  /**
   * Mapping from GSM 7bit default alphabet to Unicode.
   */
  private static final int[] GSM7_TO_UNICODE = {
     64, 163,  36, 165, 232, 233, 249, 236, 242, 199, //   0 -   9
     10, 216, 248,  13, 197, 229, 916,  95, 934, 915, //  10 -  19
    923, 937, 928, 936, 931, 920, 926,  27, 198, 230, //  20 -  29
    223, 201,  32,  33,  34,  35, 164,  37,  38,  39, //  30 -  39
     40,  41,  42,  43,  44,  45,  46,  47,  48,  49, //  40 -  49
     50,  51,  52,  53,  54,  55,  56,  57,  58,  59, //  50 -  59
     60,  61,  62,  63, 161,  65,  66,  67,  68,  69, //  60 -  69
     70,  71,  72,  73,  74,  75,  76,  77,  78,  79, //  70 -  79
     80,  81,  82,  83,  84,  85,  86,  87,  88,  89, //  80 -  89
     90, 196, 214, 209, 220, 167, 191,  97,  98,  99, //  90 -  99
    100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100 - 109
    110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110 - 119
    120, 121, 122, 228, 246, 241, 252, 224            // 120 - 127
  };
  
  /**
   * Mapping from Unicode to GSM 7bit default alphabet.
   */
  private static final int[] UNICODE_TO_GSM7 = {
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //   0 -   9
     10,  -1,  -1,  13,  -1,  -1,  -1,  -1,  -1,  -1, //  10 -  19
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //  20 -  29
     -1,  -1,  32,  33,  34,  35,   2,  37,  38,  39, //  30 -  39
     40,  41,  42,  43,  44,  45,  46,  47,  48,  49, //  40 -  49
     50,  51,  52,  53,  54,  55,  56,  57,  58,  59, //  50 -  59
     60,  61,  62,  63,   0,  65,  66,  67,  68,  69, //  60 -  69
     70,  71,  72,  73,  74,  75,  76,  77,  78,  79, //  70 -  79
     80,  81,  82,  83,  84,  85,  86,  87,  88,  89, //  80 -  89
     90,3516,3503,3518,3476,  17,  -1,  97,  98,  99, //  90 -  99
    100, 101, 102, 103, 104, 105, 106, 107, 108, 109, // 100 - 109
    110, 111, 112, 113, 114, 115, 116, 117, 118, 119, // 110 - 119
    120, 121, 122,3496,3520,3497,3517,  -1,  -1,  -1, // 120 - 129
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 130 - 139
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 140 - 149
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 150 - 159
     -1,  64,  -1,   1,  36,   3,  -1,  95,  -1,  -1, // 160 - 169
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 170 - 179
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 180 - 189
     -1,  96,  -1,  -1,  -1,  -1,  91,  14,  28,   9, // 190 - 199
     -1,  31,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  93, // 200 - 209
     -1,  -1,  -1,  -1,  92,  -1,  11,  -1,  -1,  -1, // 210 - 219
     94,  -1,  -1,  30, 127,  -1,  -1,  -1, 123,  15, // 220 - 229
     29,  -1,   4,   5,  -1,  -1,   7,  -1,  -1,  -1, // 230 - 239
     -1, 125,   8,  -1,  -1,  -1, 124,  -1,  12,   6, // 240 - 249
     -1,  -1, 126,  -1,  -1,  -1                      // 250 - 255
  };
  
  /**
   * Mapping from GSM 7bit default alphabet extension table
   * to Unicode, CP850, and CP437.
   */
  private static final int[] GSM7_EXTENSION_TABLE = {
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //   0 -   9
     10,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //  10 -  19
     94,  -1,  -1,  -1,  -1,  -1,  -1,  27,  -1,  -1, //  20 -  29
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //  30 -  39
    123, 125,  -1,  -1,  -1,  -1,  -1,  92,  -1,  -1, //  40 -  49
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //  50 -  59
     91, 126,  93,  -1, 124,  -1,  -1,  -1,  -1,  -1, //  60 -  69
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //  70 -  79
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //  80 -  89
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, //  90 -  99
     -1, 101,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 100 - 109
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1, // 110 - 119
     -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1            // 120 - 127
  };
  
  /** Charset: Undefined character set. */
  public static final int CHARSET_UNDEFINED = 0;
  /** Charset: GSM 7-bit default alphabet. */
  public static final int CHARSET_GSM7      = 1;
  /** Charset: Codepage 437. */
  public static final int CHARSET_CP437     = 2;
  /** Charset: Codepage 850. */
  public static final int CHARSET_CP850     = 3;
  /** Charset: Codepage UCS2. */
  public static final int CHARSET_UCS2      = 4;
  /* End static field values */
  
  /** Constructor unavailable, static access only. */
  private SMSCharacterSet() {}
  
  /**
   * This will convert text in GSM 7bit default alphabet into
   * Unicode character set.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static String gsmToUnicode(final String data) {
    return convert(data, GSM7_TO_UNICODE, GSM7_EXTENSION_TABLE);
  }
  
  /**
   * This will convert text in GSM 7bit default alphabet into
   * Unicode character set.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static int[] gsmToUnicode(final int[] data) {
    return convert(data, GSM7_TO_UNICODE, GSM7_EXTENSION_TABLE);
  }
  
  /**
   * This will convert text in GSM 7bit default alphabet into
   * Codepage 437.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static String gsmToCP437(final String data) {
    return convert(data, GSM7_TO_CP437, GSM7_EXTENSION_TABLE);
  }
  
  /**
   * This will convert text in GSM 7bit default alphabet into
   * Codepage 437.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static IntArrayBuilder gsmToCP437(final IntArrayBuilder data) {
    return convert(data, GSM7_TO_CP437, GSM7_EXTENSION_TABLE);
  }
  
  /**
   * This will convert text in GSM 7bit default alphabet into
   * Codepage 437.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static int[] gsmToCP437(final int[] data) {
    return convert(data, GSM7_TO_CP437, GSM7_EXTENSION_TABLE);
  }
  
  /**
   * This will convert text in GSM 7bit default alphabet into
   * Codepage 850.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static String gsmToCP850(final String data) {
    return convert(data, GSM7_TO_CP850, GSM7_EXTENSION_TABLE);
  }
  
  /**
   * This will convert text in GSM 7bit default alphabet into
   * Codepage 850.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static IntArrayBuilder gsmToCP850(final IntArrayBuilder data) {
    return convert(data, GSM7_TO_CP850, GSM7_EXTENSION_TABLE);
  }
  
  /**
   * This will convert text in GSM 7bit default alphabet into
   * Codepage 850.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static int[] gsmToCP850(final int[] data) {
    return convert(data, GSM7_TO_CP850, GSM7_EXTENSION_TABLE);
  }
  
  /**
   * This will convert text in Codepage 437 into Codepage 850.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static String cp437ToCP850(final String data) {
    return convert(data, CP437_TO_CP850, null);
  }
  
  /**
   * This will convert text in Codepage 437 into Codepage 850.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static IntArrayBuilder cp437ToCP850(final IntArrayBuilder data) {
    return convert(data, CP437_TO_CP850, null);
  }
  
  /**
   * This will convert text in Codepage 437 into Codepage 850.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static int[] cp437ToCP850(final int[] data) {
    return convert(data, CP437_TO_CP850, null);
  }
  
  /**
   * This will convert text in Codepage 850 into Codepage 437.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static String cp850ToCP437(final String data) {
    return convert(data, CP850_TO_CP437, null);
  }
  
  /**
   * This will convert text in Codepage 850 into Codepage 437.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static IntArrayBuilder cp850ToCP437(final IntArrayBuilder data) {
    return convert(data, CP850_TO_CP437, null);
  }
  
  /**
   * This will convert text in Codepage 850 into Codepage 437.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static int[] cp850ToCP437(final int[] data) {
    return convert(data, CP850_TO_CP437, null);
  }
  
  /**
   * This will convert text in Unicode into the GSM 7bit default
   * alphabet.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static String unicodeToGSM(final String data) {
    return convert(data, UNICODE_TO_GSM7, null);
  }
  
  /**
   * This will convert text in Unicode into the GSM 7bit default
   * alphabet.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static int[] unicodeToGSM(final int[] data) {
    return convert(data, UNICODE_TO_GSM7, null);
  }
  
  /**
   * This will convert text in Codepage 437 into the GSM 7bit
   * default alphabet.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static String cp437ToGSM(final String data) {
    return convert(data, CP437_TO_GSM7, null);
  }

  /**
   * This will convert text in Codepage 437 into the GSM 7bit
   * default alphabet.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static IntArrayBuilder cp437ToGSM(final IntArrayBuilder data) {
    return convert(data, CP437_TO_GSM7, null);
  }  
  
  /**
   * This will convert text in Codepage 437 into the GSM 7bit
   * default alphabet.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static int[] cp437ToGSM(final int[] data) {
    return convert(data, CP437_TO_GSM7, null);
  }
  
  /**
   * This will convert text in Codepage 850 into the GSM 7bit
   * default alphabet.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static String cp850ToGSM(final String data) {
    return convert(data, CP850_TO_GSM7, null);
  }
  
  /**
   * This will convert text in Codepage 850 into the GSM 7bit
   * default alphabet.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static IntArrayBuilder cp850ToGSM(final IntArrayBuilder data) {
    return convert(data, CP850_TO_GSM7, null);
  }
  
  /**
   * This will convert text in Codepage 850 into the GSM 7bit
   * default alphabet.
   * 
   * @param data The user data
   * @return The converted data
   */
  public static int[] cp850ToGSM(final int[] data) {
    return convert(data, CP850_TO_GSM7, null);
  }
  
  /**
   * This will change the character set of the keywords.
   * 
   * @param data The keyword data
   * @param from See CHARSET_ static field values
   * @param to See CHARSET_ static field values
   * @return The converted array
   */
  public static String changeCharacterSet(final String data,
                                          final int from,
                                          final int to) {
    if (data == null) {
      throw new NullPointerException ("Input is null");
    }
    if (from == to) {
      return data;
    }

    if (to != from) {
      switch (to) {
      case CHARSET_GSM7: // Change to GSM 7bit default alphabet
        switch (from) {
        case CHARSET_CP437: // From Codepage 437
          return cp437ToGSM(data);
          
        case CHARSET_CP850: // From Codepage 850
          return cp850ToGSM(data);
          
        default: // other values reserved, do not convert
          break;
        }
        break;
        
      case CHARSET_CP437: // Change to Codepage 437
        switch (from) {
        case CHARSET_GSM7: // From GSM 7bit default alphabet
          return gsmToCP437(data);
          
        case CHARSET_CP850: // From Codepage 850
          return cp850ToCP437(data);
          
        default: // other values reserved, do not convert
          break;
        }
        break;
        
      case CHARSET_CP850: // Change to Codepage 850
        switch (from) {
        case CHARSET_GSM7: // From GSM 7bit default alphabet
          return gsmToCP850(data);
          
        case CHARSET_CP437: // From Codepage 437
          return cp437ToCP850(data);
          
        default: // other values reserved. do not convert
          break;
        }
        break;
        
      case CHARSET_UNDEFINED: // No character set defined
        break;
        
      default: // other values reserved
        break;
      }
    }
    return data;
  }
  
  /**
   * This will change the character set of the keywords.
   * 
   * @param data The keyword data
   * @param from See CHARSET_ static field values
   * @param to See CHARSET_ static field values
   * @return The converted array
   */
  public static IntArrayBuilder changeCharacterSet(final IntArrayBuilder data,
                                                   final int from,
                                                   final int to) {
    if (data == null) {
      throw new NullPointerException ("Input is null");
    }
    if (from == to) {
      return data;
    }

    if (to != from) {
      switch (to) {
      case CHARSET_GSM7: // Change to GSM 7bit default alphabet
        switch (from) {
        case CHARSET_CP437: // From Codepage 437
          return cp437ToGSM(data);
          
        case CHARSET_CP850: // From Codepage 850
          return cp850ToGSM(data);
          
        default: // other values reserved, do not convert
          break;
        }
        break;
        
      case CHARSET_CP437: // Change to Codepage 437
        switch (from) {
        case CHARSET_GSM7: // From GSM 7bit default alphabet
          return gsmToCP437(data);
          
        case CHARSET_CP850: // From Codepage 850
          return cp850ToCP437(data);
          
        default: // other values reserved, do not convert
          break;
        }
        break;
        
      case CHARSET_CP850: // Change to Codepage 850
        switch (from) {
        case CHARSET_GSM7: // From GSM 7bit default alphabet
          return gsmToCP850(data);
          
        case CHARSET_CP437: // From Codepage 437
          return cp437ToCP850(data);
          
        default: // other values reserved. do not convert
          break;
        }
        break;
        
      case CHARSET_UNDEFINED: // No character set defined
        break;
        
      default: // other values reserved
        break;
      }
    }
    return data;
  }
  
  /**
   * This will change the character set of the keywords.
   * 
   * @param data The keyword data
   * @param from See CHARSET_ static field values
   * @param to See CHARSET_ static field values
   * @return The converted array
   */
  public static int[] changeCharacterSet(final int[] data,
                                         final int from,
                                         final int to) {
    if (data == null) {
      throw new NullPointerException ("Input is null");
    }
    if (from == to) {
      return data;
    }
    
    if (to != from) {
      switch (to) {
      case CHARSET_GSM7: // Change to GSM 7bit default alphabet
        switch (from) {
        case CHARSET_CP437: // From Codepage 437
          return cp437ToGSM(data);
          
        case CHARSET_CP850: // From Codepage 850
          return cp850ToGSM(data);
          
        default: // other values reserved, do not convert
          break;
        }
        break;
        
      case CHARSET_CP437: // Change to Codepage 437
        switch (from) {
        case CHARSET_GSM7: // From GSM 7bit default alphabet
          return gsmToCP437(data);
          
        case CHARSET_CP850: // From Codepage 850
          return cp850ToCP437(data);
          
        default: // other values reserved, do not convert
          break;
        }
        break;
        
      case CHARSET_CP850: // Change to Codepage 850
        switch (from) {
        case CHARSET_GSM7: // From GSM 7bit default alphabet
          return gsmToCP850(data);
          
        case CHARSET_CP437: // From Codepage 437
          return cp437ToCP850(data);
          
        default: // other values reserved, do not convert
          break;
        }
        break;
        
      case CHARSET_UNDEFINED: // No character set defined
        break;
        
      default: // other values reserved
        break;
      }
    }
    return data;
  }
    
  /**
   * This will convert text using a given character set mapping, and
   * an extension table.
   * <BR>
   * <BR>(such as used when converting to/from GSM 7bit default alphabet).
   * 
   * @param data The user data
   * @param mapping The character set mapping
   * @param ext The extension table, or null if not applicable
   * @return The converted data
   */
  private static String convert(final String data,
                                final int[] mapping,
                                final int[] ext) {
    if (data == null) {
      throw new NullPointerException ("Input is null");
    }
    final int length       = data.length();
    final int tblLength    = mapping.length;
    final boolean fromGSM  = ext != null;
    final int extTblLength = (fromGSM ? ext.length : 0);
    final StringBuilder output = new StringBuilder(length * 2);
    final char[] charArray = data.toCharArray();
    convertFailure   = false;
    
    for (int i = 0; i < length; i++) {
      char c = charArray[i];
      int r = (c >= tblLength ? -1 : mapping[c]);
      if (r == -1) {
        switch (c) {
        case  916: r =   16; break; // Delta character
        case  934: r =   18; break; // Phi character
        case  915: r =   19; break; // Gamma character
        case  923: r =   20; break; // Lambda character
        case  937: r =   21; break; // Omega character
        case  928: r =   22; break; // Pi character
        case  936: r =   23; break; // Psi character
        case  931: r =   24; break; // Sigma character
        case  920: r =   25; break; // Tetha character
        case  926: r =   26; break; // Xi character
        case 8364: r = 3557; break; // Euro character

        default:
          convertFailure = true;
          r = convertFailureReplacement;
          break;
        }
      }
      if (fromGSM && r == 27) { // from extension table
        if (++i >= length) {
          /*
           * Special case that should never occur, the final char
           * in the text refers to the extension table with a non-existing
           * char. Solution: Display as a space char.
           */
          convertFailure = true;
          r = convertFailureReplacement;
        }
        c = charArray[i];
        r = (c >= extTblLength ? -1 : ext[c]);
        if (r == -1) { // undefined value in extension table
          r = (c >= tblLength ? -1 : mapping[c]);
          if (r == -1) {
            convertFailure = true;
            r = convertFailureReplacement;
          }
        }
      }
      
      if (r >= 3456) { // to extension table
        r = r - 3456;
        output.append((char) 27);
      }
      output.append((char) r);
    }
    return output.toString();
  }
  
  /**
   * This will convert text using a given character set mapping, and
   * an extension table.
   * <BR>
   * <BR>(such as used when converting to/from GSM 7bit default alphabet).
   * 
   * @param data The user data
   * @param mapping The character set mapping
   * @param ext The extension table, or null if not applicable
   * @return The converted data
   */
  private static int[] convert(final int[] data,
                               final int[] mapping,
                               final int[] ext) {
    if (data == null) {
      throw new NullPointerException ("Input is null");
    }
    final int length       = data.length;
    final int tblLength    = mapping.length;
    final boolean fromGSM  = ext != null;
    final int extTblLength = (fromGSM ? ext.length : 0);
    final IntArrayBuilder output = new IntArrayBuilder(length * 2);
    convertFailure   = false;
    
    for (int i = 0; i < length; i++) {
      int c = data[i];
      int r = (c >= tblLength ? -1 : mapping[c]);
      if (r == -1) {
        switch (c) {
        case  916: r =   16; break; // Delta character
        case  934: r =   18; break; // Phi character
        case  915: r =   19; break; // Gamma character
        case  923: r =   20; break; // Lambda character
        case  937: r =   21; break; // Omega character
        case  928: r =   22; break; // Pi character
        case  936: r =   23; break; // Psi character
        case  931: r =   24; break; // Sigma character
        case  920: r =   25; break; // Tetha character
        case  926: r =   26; break; // Xi character
        case 8364: r = 3557; break; // Euro character

        default:
          convertFailure = true;
          r = convertFailureReplacement;
          break;
        }
      }
      if (fromGSM && r == 27) { // from extension table
        if (++i >= length) {
          /*
           * Special case that should never occur, the final char
           * in the text refers to the extension table with a non-existing
           * char. Solution: Display as a space char.
           */
          convertFailure = true;
          r = convertFailureReplacement;
        }
        c = data[i];
        r = (c >= extTblLength ? -1 : ext[c]);
        if (r == -1) { // undefined value in extension table
          r = (c >= tblLength ? -1 : mapping[c]);
          if (r == -1) {
            convertFailure = true;
            r = convertFailureReplacement;
          }
        }
      }
      
      if (r >= 3456) { // to extension table
        r = r - 3456;
        output.append(27);
      }
      output.append(r);
    }
    return output.toArray();
  }
      
  /**
   * This will convert text using a given character set mapping, and
   * an extension table.
   * <BR>
   * <BR>(such as used when converting to/from GSM 7bit default alphabet).
   * 
   * @param data The user data
   * @param mapping The character set mapping
   * @param ext The extension table, or null if not applicable
   * @return The converted data
   */
  private static IntArrayBuilder convert(final IntArrayBuilder data,
                                         final int[] mapping,
                                         final int[] ext) {
    if (data == null) {
      throw new NullPointerException ("Input is null");
    }
    final int length       = data.length();
    final int tblLength    = mapping.length;
    final boolean fromGSM  = ext != null;
    final IntArrayBuilder output = new IntArrayBuilder(length * 2);
    final int extTblLength = (fromGSM ? ext.length : 0);
    convertFailure   = false;
    
    for (int i = 0; i < length; i++) {
      int c = data.intAt(i);
      int r = (c >= tblLength ? -1 : mapping[c]);
      if (r == -1) {
        switch (c) {
        case  916: r =   16; break; // Delta character
        case  934: r =   18; break; // Phi character
        case  915: r =   19; break; // Gamma character
        case  923: r =   20; break; // Lambda character
        case  937: r =   21; break; // Omega character
        case  928: r =   22; break; // Pi character
        case  936: r =   23; break; // Psi character
        case  931: r =   24; break; // Sigma character
        case  920: r =   25; break; // Tetha character
        case  926: r =   26; break; // Xi character
        case 8364: r = 3557; break; // Euro character

        default:
          convertFailure = true;
          r = convertFailureReplacement;
          break;
        }
      }
      if (fromGSM && r == 27) { // from extension table
        if (++i >= length) {
          /*
           * Special case that should never occur, the final char
           * in the text refers to the extension table with a non-existing
           * char. Solution: Display as a space char.
           */
          convertFailure = true;
          r = convertFailureReplacement;
        }
        r = (c >= extTblLength ? -1 : ext[c]);
        if (r == -1) { // undefined value in extension table
          r = (c >= tblLength ? -1 : mapping[c]);
          if (r == -1) {
            convertFailure = true;
            r = convertFailureReplacement;
          }
        }
      }
      
      if (r >= 3456) { // to extension table
        r -= 3456;
        output.append(27);
      }
      output.append(r);
    }
    return output;
  }
}
